package exchange

import (
	"global/model"
	"strings"
	"global/libs/structs"
	"time"
	"global/core"
	"github.com/garyburd/redigo/redis"
	"github.com/ngaut/log"
	"sync"
	"github.com/shopspring/decimal"
	"global/constants"
	"srv/analysis/lib/coin"
	"os"
)

type tickerDatas struct {
	from 			string
	to 				string
	volume 			decimal.Decimal
	amount 			decimal.Decimal
	open 			decimal.Decimal
	high 			decimal.Decimal
	low 			decimal.Decimal
	close 			decimal.Decimal
	percent 		decimal.Decimal
}

type AnalysisExchange struct {

	Pool 					*redis.Pool
	SortHandle 				*ExchangeSort

	CoinHandler 			*coin.CoinHandler

	// 交易所别名
	Slug					string

	// 交易所元数据
	Exchange 				*model.Exchange
	// 该交易所交易对
	Symbols 				map[string]structs.Symbol
	// 该交易所交易市场
	Markets 				map[string]interface{}

	// 停留时间
	Frequency 				time.Duration

	// 该交易所所有币种
	coins 					map[string]interface{}

	coinInPairs 			map[string]map[structs.Symbol]interface{}

	// 美元兑人民币汇率
	usdToCnyRate 			decimal.Decimal

	// 退出信号
	ExitSignal 				chan interface{}
	ForceUpdateSignal 		chan *model.Exchange
	wg 						sync.WaitGroup
	mux 					sync.Mutex
}

func NewAnalysisExchange(
	pool *redis.Pool,
	coinhandle *coin.CoinHandler,
	sortHandle *ExchangeSort,
	exchange *model.Exchange,
	markets map[string]interface{},
	symbols map[string]structs.Symbol) *AnalysisExchange {
	slug := strings.ToLower(exchange.Slug)
	return &AnalysisExchange{
		Slug: 				slug,
		Exchange: 			exchange,
		Markets: 			markets,
		Pool: 				pool,
		Symbols: 			symbols,
		CoinHandler: 		coinhandle,
		coinInPairs: 		make(map[string]map[structs.Symbol]interface{}),
		SortHandle: 		sortHandle,

		ExitSignal: 		make(chan interface{}),
		ForceUpdateSignal: 	make(chan *model.Exchange),
		Frequency: 			time.Second * 2,
	}
}

// 初始化获取交易所交易币种
func (a *AnalysisExchange) Init() {
	coins := make(map[string]interface{})
	for _, s := range a.Symbols {
		coins[s.From] = true
		coins[s.To] = true
	}
	a.coins = coins
	a.updateMetaData()
}

func (a *AnalysisExchange) Start() error {

	// init
	a.Init()

	for {
		select {
		case <- a.ExitSignal:
			return nil
		case d := <- a.ForceUpdateSignal:
			a.Exchange = d
			a.updateMetaData()
		default:
		}

		a.handler()
		time.Sleep(a.Frequency)
	}
	return nil
}

// 处理器
func (a *AnalysisExchange) handler() {

	start := time.Now()
	tickers, pairTickers, err := a.getAllTickers()
	if err != nil {
		log.Errorf("Error: get all tickers failed, msg: %s", err)
		return
	}

	totalAmount := decimal.Zero

	// 计算每个交易币种含有的交易对
	a.calcCoinToSymbols()


	usdPriceForCoins := a.coinPrice(tickers)
	for s, v := range usdPriceForCoins {
		totalAmount = totalAmount.Add(v.Volume24h.Mul(v.CloseUSD))
		v.AmountUSD = v.Volume24h.Mul(v.CloseUSD)
		v.NumPairs = len(a.coinInPairs[s])
	}

	a.sendSortData(totalAmount)

	// 每个币的 ticker 数据 和 交易总额
	conn := a.Pool.Get()
	defer conn.Close()

	// 0. 获取人民币汇率
	a.getCnyUsdRate(conn)

	// 1, 保存交易量
	exchangeTicker := structs.ExchangeTicker{
		Exchange: 		a.Slug,
		AmountUsd: 		totalAmount.StringFixedBank(8),
		CountPairs: 	len(a.Symbols),
		CountMarkets: 	len(a.Markets),
		CountCoins: 	len(usdPriceForCoins),
	}
	if err := core.SaveExchangeVolume(conn, a.Slug, exchangeTicker); err != nil {
		log.Errorf("Error: %s", err)
	}

	// 2. 保存交易区
	if err := core.SaveExchangeMarkets(conn, a.Slug, a.Markets); err != nil {
		log.Errorf("Error: %s", err)
	}

	// 3. 保存币种及数据
	err = core.SaveCoinUsdPrice(conn, a.Slug, usdPriceForCoins)
	if err != nil {
		log.Errorf("Error: %s", err)
	}

	// 4. 保存币种在该交易所对应的交易对
	if err = core.SaveCoinInExchange(conn, a.Slug, a.coinInPairs); err != nil {
		log.Errorf("Error: %s", err)
	}

	// 5. 交易对价格换算之后保存
	newTickers, err := a.getAndSaveCoinPairPriceToUSD(conn, usdPriceForCoins, pairTickers)
	if err != nil {
		log.Errorf("Error: %s", err)
	}

	// 6. 排序
	a.sort(newTickers)


	a.CoinHandler.ExchangeCoinPrice <- &structs.CoinUsdPriceChannelData{
		Exchange: 	a.Slug,
		Data: 		usdPriceForCoins,
	}

	log.Debugf("exchange handle time: ", time.Since(start))
}

func (a *AnalysisExchange) sort(tickers []*structs.Ticker)  {
	h := NewPairSort(a.Pool, a.Slug)
	h.StartSort(tickers)
}

func (a *AnalysisExchange) sendSortData(totalAmount decimal.Decimal) {
	log.Infof("send data: %v", totalAmount)
	a.SortHandle.receive <- &sortExchangeAmountData{
		Exchange: 	a.Slug,
		Amount: 	totalAmount,
	}
}

func (a *AnalysisExchange) getCnyUsdRate(conn redis.Conn)  {
	rate, err := core.GetCnyRateByCoin(conn, "usd")
	if err != nil {
		log.Errorf("Error: %s", err)
		os.Exit(1)
	}
	a.usdToCnyRate, _ = decimal.NewFromString(rate)
}

func (a *AnalysisExchange) getAndSaveCoinPairPriceToUSD(
	conn redis.Conn,
	coinPrice map[string]*structs.CoinUsdPrice,
	tickers []*structs.Ticker) ([]*structs.Ticker, error) {

	newTickers := make([]*structs.Ticker, 0)
	for _, v := range tickers {
		usd, _ := decimal.NewFromString(v.PriceUsd)
		cny, _ := decimal.NewFromString(v.PriceCny)
		if usd.Equal(decimal.Zero) || cny.Equal(decimal.Zero) {
			p, ok := coinPrice[v.From]
			if !ok { continue }
			usd = p.CloseUSD
			cny = p.CloseUSD.Mul(a.usdToCnyRate)
		}
		volume, _ := decimal.NewFromString(v.Volume)
		v.AmountUsd = volume.Mul(usd).StringFixedBank(8)
		v.AmountCny = volume.Mul(cny).StringFixedBank(8)
		v.PriceUsd = usd.StringFixedBank(4)
		v.PriceCny = cny.StringFixedBank(4)

		newTickers = append(newTickers, v)
	}

	return newTickers, core.SaveCoinPairTickerToAnalysis(conn, a.Slug, newTickers)
}

// 计算
func (a *AnalysisExchange) calcCoinToSymbols() {
	coinSymbolsMaps := make(map[string]map[structs.Symbol]interface{})
	for _, v := range a.Symbols {
		if _, ok := coinSymbolsMaps[v.From]; !ok {
			coinSymbolsMaps[v.From] = make(map[structs.Symbol]interface{})
		}
		if _, ok := coinSymbolsMaps[v.To]; !ok {
			coinSymbolsMaps[v.To] = make(map[structs.Symbol]interface{})
		}
		coinSymbolsMaps[v.From][v] = true
		coinSymbolsMaps[v.To][v] = true
	}

	a.mux.Lock()
	a.coinInPairs = coinSymbolsMaps
	a.mux.Unlock()
}

// 得到每个币的价格
func (a *AnalysisExchange) coinPrice(
	tickerMaps map[string]map[structs.Symbol]*tickerDatas) map[string]*structs.CoinUsdPrice {

	// 每个币种的美元价格
	found := make(map[string]*structs.CoinUsdPrice)
	notFound := make(map[string]map[structs.Symbol]*tickerDatas)

	// 先找到 USDT 的交易区
	if tickers, ok := tickerMaps[constants.USDT]; ok {
		for symbol, t := range tickers {
			found[symbol.From] = &structs.CoinUsdPrice{
				OpenUSD: 		t.open,
				HighUSD: 		t.high,
				LowUSD:			t.low,
				CloseUSD: 		t.close,
				Volume24h: 		t.volume,			// 币种交易量
			}
		}
		found[constants.USDT] = a.getUsdPrice()
	} else if tickers, ok := tickerMaps[constants.USD]; ok {
		for symbol, t := range tickers {
			found[symbol.From] = &structs.CoinUsdPrice{
				OpenUSD: 		t.open,
				HighUSD: 		t.high,
				LowUSD:			t.low,
				CloseUSD: 		t.close,
				Volume24h: 		t.volume,			// 币种交易量
			}
		}
		found[constants.USD] = a.getUsdPrice()
	} else {
		// TODO 暂时未考虑到没有 USDT 交易区的情况
		return found
	}

	for market, tickers := range tickerMaps {
		if market == constants.USDT || market == constants.USD { continue }
		toP, ok := found[market]
		if !ok {
			// 将 USDT 交易对下面没有找到这个币种的时候，无法获取对应的币种的价格
			notFound[market] = tickers
			continue
		}
		// 循环某个交易区下面的交易对
		for s, t := range tickers {
			// 某个币种的交易量求和
			if _, ok := found[s.From]; ok {
				found[s.From].Volume24h = found[s.From].Volume24h.Add(t.volume)
				continue
			}

			found[s.From] = &structs.CoinUsdPrice{
				OpenUSD: 		toP.OpenUSD.Mul(t.open),
				HighUSD: 		toP.HighUSD.Mul(t.high),
				LowUSD: 		toP.LowUSD.Mul(t.low),
				CloseUSD: 		toP.CloseUSD.Mul(t.close),
				Volume24h: 		t.volume,
			}
		}
	}

	// 获取未获取到价格的交易区的价格
	leftMarketPrice := make(map[string]*structs.CoinUsdPrice)
	for sTo, maps := range notFound {
		for k, u := range maps {
			// 如果 coin from 在found中已经存在
			if f, ok := found[k.From]; ok {
				var open, low, high, closep decimal.Decimal
				if !u.open.Equal(decimal.Zero) {
					open = f.OpenUSD.Div(u.open)
				}
				if !u.high.Equal(decimal.Zero) {
					high = f.HighUSD.Div(u.high)
				}
				if !u.low.Equal(decimal.Zero) {
					low = f.LowUSD.Div(u.low)
				}
				if !u.close.Equal(decimal.Zero) {
					closep = f.CloseUSD.Div(u.close)
				}
				leftMarketPrice[sTo] = &structs.CoinUsdPrice{
					OpenUSD: 		open,
					HighUSD: 		high,
					LowUSD: 		low,
					CloseUSD: 		closep,
				}
				break
			}
		}
	}

	for symbolTo, maps := range notFound {
		var ok bool
		var to *structs.CoinUsdPrice
		if to, ok = leftMarketPrice[symbolTo]; !ok { continue }
		for s, u := range maps {
			if old, ok := found[s.From]; ok {
				found[s.From].Volume24h = found[s.From].Volume24h.Add(old.Volume24h)
				continue
			}

			found[s.From] = &structs.CoinUsdPrice{
				OpenUSD: 		to.OpenUSD.Mul(u.open),
				HighUSD: 		to.HighUSD.Mul(u.high),
				LowUSD: 		to.LowUSD.Mul(u.low),
				CloseUSD: 		to.CloseUSD.Mul(u.close),
				Volume24h: 		u.volume,
			}
		}

		if _, ok := found[symbolTo]; !ok {
			found[symbolTo] = to
		}
	}

	return found
}

// 获取USD价格
func (a *AnalysisExchange) getUsdPrice() *structs.CoinUsdPrice {
	return &structs.CoinUsdPrice{
		OpenUSD: 		decimal.NewFromFloat(1),
		HighUSD: 		decimal.NewFromFloat(1),
		LowUSD: 		decimal.NewFromFloat(1),
		CloseUSD: 		decimal.NewFromFloat(1),
	}
}

// 获取器所有交易对的 Ticker 信息
// 结果格式化 decimal.Decimal 类型
func (a *AnalysisExchange) getAllTickers() (map[string]map[structs.Symbol]*tickerDatas, []*structs.Ticker, error) {
	conn := a.Pool.Get()
	defer conn.Close()

	exchangeTickers := make(map[string]map[structs.Symbol]*tickerDatas)
	tickers, err := core.GetMultiTickersFromExchanges(conn, a.Slug, a.Symbols)
	if err != nil {
		return nil, nil, err
	}

	a.mux.Lock()
	defer a.mux.Unlock()

	for _, t := range tickers {
		if symbol, ok := a.Symbols[t.Symbol]; ok {
			if _, ok := exchangeTickers[symbol.To]; !ok {
				exchangeTickers[symbol.To] = make(map[structs.Symbol]*tickerDatas)
			}
			exchangeTickers[symbol.To][symbol] = a.getSymbolTicker(symbol, t)
		}
	}

	return exchangeTickers, tickers, nil
}

// 获取单个交易对的Ticker详情
func (a *AnalysisExchange) getSymbolTicker(symbol structs.Symbol, t *structs.Ticker) *tickerDatas {

	volume, _ := decimal.NewFromString(t.Volume)
	amount, _ := decimal.NewFromString(t.Amount)
	open, _ := decimal.NewFromString(t.Open)
	high, _ := decimal.NewFromString(t.High)
	low, _ := decimal.NewFromString(t.Low)
	price, _ := decimal.NewFromString(t.Close)
	p, _ := decimal.NewFromString(t.Percent)

	return &tickerDatas{
		from: 			t.From,
		to: 			t.To,
		volume: 		volume,
		amount: 		amount,
		high: 			high,
		open: 			open,
		low: 			low,
		close: 			price,
		percent: 		p,
	}
}

// 修改交易所元数据
func (a *AnalysisExchange) updateMetaData() {
	conn := a.Pool.Get()
	defer conn.Close()

	baseData := structs.ExchangeBase{
		Mid: 			a.Exchange.Mid,
		NameCn: 		a.Exchange.NameCn,
		NameEn: 		a.Exchange.NameEn,
		Slug: 			a.Exchange.Slug,
		Logo: 			a.Exchange.Logo,
		Description: 	a.Exchange.Description,
		Mode: 			a.Exchange.Mode,
		Country: 		a.Exchange.Country,
		Website: 		a.Exchange.Website,
		Twitter: 		a.Exchange.Twitter,
		Blog: 			a.Exchange.Blog,
		Chat: 			a.Exchange.Chat,
		Fee: 			a.Exchange.Fee,
		Sort: 			a.Exchange.Sort,
		Enable: 		a.Exchange.Enable,
		Recommend: 		a.Exchange.Recommend,
		RecommendSort: 	a.Exchange.RecommendSort,
	}
	_, err := core.SaveExchangeMetaData(conn, baseData)
	if err != nil {
		log.Errorf("Error: update exchange metadata failed, msg: %s", err.Error())
		return
	}
}